﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace nr.Tombola.Net
{
    /// <summary>
    /// Il server di gioco.
    /// </summary>
    /// <remarks>Gestisce i client che intendono partecipare alla partita.</remarks>
    public class GameServer
    {
        /// <summary>
        /// Comunica la connessione di un client.
        /// </summary>
        public event ClientAcceptedEventHandler ClientAccepted;

        /// <summary>
        /// Comunica la disconnessione di un client. 
        /// </summary>
        public event ClientRemovedEventHandler ClientRemoved;

        /// <summary>
        /// Costruttore di default.
        /// </summary>
        public GameServer() : this(9090) { }

        /// <summary>
        /// Costruttore.
        /// </summary>
        /// <param name="port">Porta di ascolto del server.</param>
        public GameServer(int port) : this(System.Net.IPAddress.Any, port) { }

        /// <summary>
        /// Costruttore.
        /// </summary>
        /// <param name="localAddress">Indirizzo locale sul quale il server è in ascolto.</param>
        /// <param name="port">Porta di ascolto del server.</param>
        public GameServer(string localAddress, int port) : this(System.Net.IPAddress.Parse(localAddress), port) { }

        /// <summary>
        /// Costruttore.
        /// </summary>
        /// <param name="localAddress">Indirizzo locale sul quale il server è in ascolto.</param>
        /// <param name="port">Porta di ascolto del server.</param>
        public GameServer(System.Net.IPAddress localAddress, int port)
        {
            LocalAddress = localAddress;
            Port = port;
            Running = false;
            Clients = new Dictionary<Guid, System.Net.Sockets.TcpClient>();
        }

        /// <summary>
        /// Gestore della comunicazione con i client.
        /// </summary>
        private System.Net.Sockets.TcpListener Listener { get; set; }

        /// <summary>
        /// Thread in cui il server resta in ascolto per consentire la connessione dei clients.
        /// </summary>
        private System.Threading.Thread ListeningThread { get; set; }

        /// <summary>
        /// Indirizzo locale sul quale il server è in ascolto.
        /// </summary>
        public System.Net.IPAddress LocalAddress { get; set; }

        /// <summary>
        /// Porta di ascolto del server.
        /// </summary>
        public int Port { get; set; }

        /// <summary>
        /// Indica se il server è in ascolto.
        /// </summary>
        public bool Running { get; private set; }

        /// <summary>
        /// Elenco dei clients attualmente collegati.
        /// </summary>
        private Dictionary<Guid, System.Net.Sockets.TcpClient> Clients { get; set; }

        /// <summary>
        /// Numero di clients connessi.
        /// </summary>
        public int ConnectedClients { get { return Clients.Count; } }

        /// <summary>
        /// Avvia il server.
        /// </summary>
        public void Start()
        {
            Listener = new System.Net.Sockets.TcpListener(LocalAddress, Port);
            Listener.Start();
            Running = true;
            ListeningThread = new System.Threading.Thread(HandleClients);
            ListeningThread.Start();
        }

        /// <summary>
        /// Routine di ascolto del server.
        /// </summary>
        private void HandleClients()
        {
            while (Running)
            {
                System.Net.Sockets.TcpClient client = Listener.AcceptTcpClient();
                Guid id = Guid.NewGuid();
                Clients.Add(id, client);
                SendMessage(client, string.Format("110:GUID:{0}", id.ToString()));
                OnClientConnected(new ClientAcceptedEventArgs(ConnectedClients, id));
            }
        }

        /// <summary>
        /// Ferma il server.
        /// </summary>
        public void Stop()
        {
            Running = false;
            SendMessage("111:Bye");
            foreach (System.Net.Sockets.TcpClient client in Clients.Values) client.Close();
            ListeningThread.Abort();
            Listener.Stop();
        }

        /// <summary>
        /// Comunica ai client l'inizio di una nuova partita.
        /// </summary>
        public void StartGame()
        {
            SendMessage("001:START");
        }

        /// <summary>
        /// Comunica l'estrazione di un numero ai client.
        /// </summary>
        /// <param name="number">Numero che è stato estratto.</param>
        public void Extract(int number)
        {
            SendMessage(string.Format("010:DRAW:{0}", number));
        }

        /// <summary>
        /// Comunica ai client la fine di una partita.
        /// </summary>
        public void EndGame()
        {
            SendMessage("100:END");
        }

        /// <summary>
        /// Invia un messaggio ad un client.
        /// </summary>
        /// <param name="client">Client al quale inviare il messaggio.</param>
        /// <param name="message">Messaggio da inviare.</param>
        protected void SendMessage(System.Net.Sockets.TcpClient client, string message)
        {
            System.IO.StreamWriter sw = new System.IO.StreamWriter(client.GetStream(), System.Text.Encoding.ASCII);
            sw.WriteLine(message);
            sw.Flush();
        }

        /// <summary>
        /// Invia un messaggio specifico ad un client identificato attraverso l'identificativo assegnato in fase di collegamento.
        /// </summary>
        /// <param name="clientID">ID del client al quale inviare il messaggio.</param>
        /// <param name="message">Messaggio da inviare.</param>
        public void SendMessage(Guid clientID, string message)
        {
            if (Clients.Keys.Contains(clientID))
                SendMessage(Clients[clientID], message);
        }

        /// <summary>
        /// Invia un messaggio a tutti i client connessi.
        /// </summary>
        /// <param name="message">Messaggio da inviare.</param>
        public void SendMessage(string message)
        {
            foreach (Guid id in Clients.Keys)
            {
                System.Net.Sockets.TcpClient client = Clients[id];
                if ((client != null) && client.GetStream().CanWrite)
                    SendMessage(client, message);
                else
                    RemoveClient(id);
            }
        }

        /// <summary>
        /// Rimuove un client dalla lista dei client collegati.
        /// </summary>
        /// <param name="id"></param>
        private void RemoveClient(Guid id)
        {
            Clients[id].Close();
            Clients.Remove(id);
            OnClientDisconnected(new ClientRemovedEventArgs(ConnectedClients, id));
        }

        /// <summary>
        /// Solleva l'evento ClientDisconnected.
        /// </summary>
        /// <param name="e">Argomenti dell'evento.</param>
        protected virtual void OnClientDisconnected(ClientRemovedEventArgs e)
        {
            if (ClientRemoved != null) ClientRemoved(this, e);
        }

        /// <summary>
        /// Solleva l'evento ClientConnected.
        /// </summary>
        /// <param name="e">Argomenti dell'evento.</param>
        protected virtual void OnClientConnected(ClientAcceptedEventArgs e)
        {
            if (ClientAccepted != null) ClientAccepted(this, e);
        }
    }
}
